DearMiku

OpenGL ES on iOS --- 2D纹理

字数统计: 1.9k阅读时长: 8 min
2017/11/29 Share

简介

纹理是用来丰富我们绘制物体细节的,它可以是一张2D图片(除了图像外,纹理也被用来存储大量数据,传递到着色器上),就像贴图一样贴在绘制的物体上.

纹理属性

纹理坐标

为了将纹理映射到绘制的物体上,我们需要指定 某个顶点对应着 纹理的那个位置. 通过纹理坐标,标明顶点从纹理图像那一部分采样,之后在图形的其它片段进行片段插值.

纹理坐标系和顶点坐标系有所不同,顶点坐标系 (0,0)点位于窗口中心. 纹理坐标系 (0,0)点位于 纹理左下角.

顶点坐标系
顶点坐标系

纹理坐标系
纹理坐标系

纹理环绕方式

当我们将顶点位置设置到纹理坐标之外时,则需要设置纹理环绕方式 来显示纹理图案

环绕属性 效果
GL_REPEAT 重复纹理图案(默认)
GL_MIRRORED_REPEAT 镜像重复纹理图案
GL_CLAMP_TO_EDGE 将纹理锁定在0~1之间,超出部分重复纹理边缘图案,产生拉伸效果
GL_CLAMP_TO_BORDER 超出部分为用户指定边缘颜色

环绕示意图

纹理环绕函数

glTexParameteri (GLenum target, GLenum pname, GLint param);
参数:

target: 指定纹理目标,若为2D纹理 则为 GL_TEXTURE_2D
pname: 对应的纹理坐标轴(这里 s,t,r 对应 x,y,z) GL_TEXTURE_WRAP_S ,GL_TEXTURE_WRAP_T
param: 环绕方式,填入上面的方式.

对2D纹理时,必须对 s,t坐标轴都进行设置. 若是设置 GL_CLAMP_TO_BORDER 形式,则还需要额外设置 环绕颜色

1
2
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

纹理过滤

纹理坐标是不依赖于 纹理大小和分辨率的, 1就表示纹理的边缘.但是物体和和纹理大小可能不一致,这就造成了对纹理的放大和拉伸.这时如何将纹理像素映射到纹理坐标上,就需要我们设置. 该属性就是 纹理过滤.

纹理过滤有很多种,下面是最重要的两种

邻近过滤

GL_NEAREST 是默认的纹理过滤方式,它会选择距离 纹理坐标最近的像素点作为样本颜色.当纹理被放大时,会有颗粒感
临近过滤

线性过滤

GL_LINEAR, 会基于当前纹理坐标附近的像素点计算一个插值,也就是附近纹理的混合色,离得越近的像素点,颜色贡献越大,当纹理被放大时,会比临近过滤更平滑.
线性过滤临近过滤和线性过滤比较

过滤函数

我们需要对放大(Magnify)和缩小(Minify)的情况设置过滤效果

1
2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

多级渐远纹理

在3D世界,根据物体的远近不同,物体也存在缩放的效果,要显示不同的分辨率,若都使用相同的分辨率,一是会使物体产生不真实的效果,二是会造成内存的浪费. 在研究3D纹理时再说.~~

纹理代码

首先是设置 上下文,着色器,程序对象…的方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
context1 = [[EAGLContext alloc] initWithAPI:(kEAGLRenderingAPIOpenGLES3)];
BOOL isSetCOntextRight = [EAGLContext setCurrentContext:context1];
if (!isSetCOntextRight) {
printf("设置Context失败");
}

NSString* verStr = [[NSBundle mainBundle] pathForResource:@"Texture2D_Vert.glsl" ofType:nil];
NSString* fragStr = [[NSBundle mainBundle]pathForResource:@"Texture2D_Frag.glsl" ofType:nil];

program1 = createGLProgramFromFile(verStr.UTF8String, fragStr.UTF8String);
glUseProgram(program1);

//创建,绑定渲染缓存 并分配空间
glGenRenderbuffers(1, &renderBuf1);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuf1);
// 为 color renderbuffer 分配存储空间
[context1 renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];

//创建,绑定帧缓存 并分配空间
glGenFramebuffers(1, &frameBuf1);
// 设置为当前 framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, frameBuf1);
// 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, renderBuf1);


glClearColor(1.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);

然后是 绘制物体的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
float verData[] = {
// 位置 颜色 纹理坐标
0.5f,0.5f,0.0f, 1.0f,0.0f,0.0f, 1.0f,1.0f,
0.5f,-0.5f,0.0f, 0.0f,1.0f,0.0f, 1.0f,0.0f,
-0.5f,-0.5f,0.0f, 0.0f,0.0f,1.0f, 0.0f,0.0f,
-0.5f,0.5f,0.0f, 1.0f,1.0f,0.0f, 0.0f,1.0f,
};
unsigned int indices[] = {
0,1,3,
1,2,3
};




glGenVertexArrays(1, &VAO1);
glGenBuffers(1, &VBO1);
glGenBuffers(1, &EBO1);

glBindVertexArray(VAO1);
glBindBuffer(GL_ARRAY_BUFFER, VBO1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO1);

glBufferData(GL_ARRAY_BUFFER, sizeof(verData), verData, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)(3*sizeof(float)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)(6*sizeof(float)));

glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);

纹理设置~~

stbi_image 一个非常流行的单头文件图像加载库 stbi_image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//因为使用 stbi 函数导入的图片会颠倒,所以需要将其摆正
stbi_set_flip_vertically_on_load(true);

NSString* imPath = [[NSBundle mainBundle] pathForResource:@"wall.jpg" ofType:nil];
int width,height,nrChannels;

//加载图片
unsigned char * imdata = stbi_load(imPath.UTF8String, &width, &height, &nrChannels, 0);

//创建 纹理
unsigned int texture;
glGenTextures(1, &texture);

//激活纹理单元0
glActiveTexture(GL_TEXTURE0);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, texture);
//将图像传入纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imdata);

//glGenerateMipmap(GL_TEXTURE_2D);

//设置纹理环绕和纹理过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

//将不用的图像释放
stbi_image_free(imdata);

//将纹理作为统一变量传入显存
glUniform1i(glGetUniformLocation(program1, "outTexture"), 0);


//这里PNG格式 通过stbi_load 拉入时 会导致产生 BGRA格式(我也不大清楚原因,懂的朋友 告告我 先O(∩_∩)O谢谢了) ,这时 图片作为纹理显示时色彩会出错.所以将其转换一蛤~
NSString* imPath1 = [[NSBundle mainBundle] pathForResource:@"face.png" ofType:nil];
int width1,height1,nrChannels1;
unsigned char * imdata1 = stbi_load(imPath1.UTF8String, &width1, &height1, &nrChannels1, STBI_rgb_alpha);
for (int i = 0; i<width1*height1; i++ ) {
char tR = imdata1[i*4+2];
imdata1[i*4+2] = imdata1[i*4];
imdata1[i*4] = tR;
}

unsigned int texture1;

glGenTextures(1, &texture1);
glActiveTexture(GL_TEXTURE1); //必须先写这个再绑定
glBindTexture(GL_TEXTURE_2D, texture1);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width1, height1, 0, GL_RGBA, GL_UNSIGNED_BYTE, imdata1);
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(imdata1);

glUniform1i(glGetUniformLocation(program1, "outTexture1"), 1);

//绘制显示
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
[context1 presentRenderbuffer:GL_RENDERBUFFER];

片段着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#version 300 es

precision mediump float;


in vec2 outTexCoord;

uniform sampler2D outTexture;
uniform sampler2D outTexture1;

in vec3 outColor;

out vec4 FragColor;

void main()
{
FragColor = mix(texture(outTexture,outTexCoord),texture(outTexture1,outTexCoord),0.2);
}

GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。

函数补充说明

glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels);

target 纹理目标 设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理
level 为纹理指定多级渐远纹理的级别,0表示基本级别
internalformat 希望将纹理存储为何等格式
width height 图像宽高
border 设置为0 说是历史遗留问题
format 源图格式 type 数据格式
pixels 图像数据

纹理单元

在代码中 使用 glUniform1i方法进行纹理传递 是因为 纹理单元 这个概念.
使用glUniform1i可以为纹理采样器分配一个位置值,通过把纹理单元赋值给采样器,就可以一次绑定多个纹理.

OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

需要注意的是 在绑定纹理时 先要激活纹理单元才可以

显示结果
GitHub

CATALOG
  1. 1. 简介
  2. 2. 纹理属性
    1. 2.1. 纹理坐标
    2. 2.2. 纹理环绕方式
      1. 2.2.1. 纹理环绕函数
    3. 2.3. 纹理过滤
      1. 2.3.1. 邻近过滤
      2. 2.3.2. 线性过滤
      3. 2.3.3. 过滤函数
    4. 2.4. 多级渐远纹理
  3. 3. 纹理代码
    1. 3.1. 纹理设置~~
      1. 3.1.1. 函数补充说明
      2. 3.1.2. 纹理单元